#!/bin/sh

#
# sievepatch - patch every Sieve script of every user
#
# Copyright 2024  Odin Kroeger
#
# This file is part of SieveManager.
#
# SieveManager is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# SieveManager is distributed in the hope that it will be useful,
# but WITHOUT ALL WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with SieveManager. If not, see
# <https://www.gnu.org/licenses/>.
#

set -Cefu


#
# Functions
#

# Terminate children and clean up before exiting
atexit() {
	__atexit_retval=$?
	trap '' EXIT HUP INT TERM
	set +e
	kill -s TERM -- $(jobs -p) -$$ 2>/dev/null
	wait
	eval "${atexit-}"
	unset atexit
	return "$__atexit_retval"
}

# Catch a signal, clean up, and re-raise the signal
catch() {
	caught="${1:?}"
	[ "${catch-}" ] || return 0
	atexit
	trap - "$caught"
	kill -s "$caught" "$$"
}


#
# Defaults
#

# Configuration file
rcfile=/etc/sieve/sievepatchrc

# Command that prints one username per line to standard output
getusers='cut -d: -f1 /etc/passwd'
#getusers="ldapsearch -LLL -x uid | sed -n 's/uid: //p'"
#getusers="mysql -uuser -ppassword -e'select user from mail.users'"

# Arguments to SieveManager
sieveargs=

# SieveManager configuration file
sieveconfig=/etc/sieve/config

# Seconds to wait for SieveManager to exit
timeout=60


#
# Options
#

while getopts 'c:dho:' opt
do
	case $opt in
	(h)	exec cat <<-EOF
		sievepatch - patch every Sieve script of every user

		Usage:  sievepatch [-c file] [-d] [-o key=value]
		                   patchfile [arg ...]

		Options:
		    -c file       Read configuration from file.
		    -d            Enable debugging mode.
		    -o key=value  Set configuration key to value.

		Arguments are passed on to sievemgr.
		EOF
	(c)	rcfile="$OPTARG" ;;
	(d)	set -x ;;
	(o)	key="${OPTARG%%=*}" value="${OPTARG#*=}"
		eval "${key:?}"='"${value:?}"'
		;;
	esac
done
shift $((OPTIND - 1))

[ -e "$rcfile" ] && . "$rcfile"

readonly getusers rcfile sieveargs sieveconfig


#
# Operands
#

if [ "$#" -lt 1 ]
then
	printf 'usage: sievepatch patchfile\n' >&2
	exit 2
fi

readonly patchfile="$1"
shift


#
# Init
#

# Set up clean-up and signal handling
trap atexit EXIT
for sig in HUP INT TERM
do trap "catch $sig" "$sig"
done

# Suppress signal handling during init
catch=

# Create a temporary directory
readonly tmpdir="${TMPDIR:-/tmp}/svp-$$"
mkdir -m0700 "$tmpdir"
atexit="rm -rf \"\$tmpdir\"; ${atexit-}"

# Create I/O pipes
send="$tmpdir/send" recv="$tmpdir/recv"
mkfifo -m0600 "$send" "$recv"

# Attach a SieveManager session to one end of each pipe
sievemgr -Cc"$sieveconfig" $sieveargs "$@" <"$send" >"$recv" & pid=$!

# Attach file descriptors to the other end of each pipe
exec 3>"$send"
exec 4<"$recv"
atexit="exec 3>&-; exec 4<&-; ${atexit-}"

# Re-enable signal handling and re-raise the last signal
catch=y
[ "${caught-}" ] && kill -s "$caught" "$$"

# Change the working directory of the SieveManager session
readonly scriptdir="$tmpdir/scripts"
mkdir "$scriptdir"
cat <<-EOF >&3
	xargs cd
	$scriptdir

EOF


#
# Main
#

# Loop over users
eval $getusers |
while read -r user
do
	# Send commands to sievemgr
	cat <<-EOF >&3
		su $user
		get *
		echo fin
	EOF

	# Wait until sievemgr has executed "echo fin"
	while read -r line && [ "$line" != fin ]
	do :
	done <&4

	# Loop over the downloaded scripts
	find "$scriptdir" -type f |
	while read -r orig
	do
		# Patch the downloaded files
		patched="$orig.patched"
		patch -i"$patchfile" -o"$patched" "$orig"

		# Skip upload if no changes were made
		if ! cmp -s "$orig" "$patched"
		then
			# Upload the patched file
			echo xargs put -fo  >&3
			basename "$orig"    >&3
			basename "$patched" >&3

			# Send an empty line to make xargs stop reading
			echo >&3
		fi

		# Remove the local files
		rm -f "$orig" "$patched"
	done
done

# Tell SieveManager to exit
echo exit >&3
wait "$pid"
